
//
// This file defines the public Sass API to ag-Grid's styles.
// https://ag-grid.com/javascript-data-grid/global-style-customisation-sass/
//

@use "sass:map";
@use "sass:list";
@use "sass:meta";
@use "sass:string";
@use "sass:color";
@use "sass:math";
@use "./css-content";
@use "./icon-font-codes" as *;

@forward "./shared";

// Emit styles for the grid. This mixin validates the parameters passed to it,
// converts the parameters to CSS variables, and combines all the necessary CSS
// files for the grid and selected theme.
@mixin grid-styles($global-params: ()) {
    $no-native-widgets: map.get($global-params, "suppress-native-widget-styling");
    $global-params: map.remove($global-params, "suppress-native-widget-styling");

    $themes: -get-themes($global-params);

    @if $no-native-widgets {
        @include -load-css-file("ag-grid-no-native-widgets.css");
    } @else {
        @include -load-css-file("ag-grid.css");
    }

    $global-extended-theme-name: -clean-theme-name(map.get($global-params, "extend-theme"));

    @each $theme-name, $theme-params in $themes {
        @include -validate-params($theme-params, $theme-name);

        @include -load-theme-css-file($theme-name);
        $extended-theme-name: -get-extended-theme-name($theme-params, $default: $global-extended-theme-name);
        @if $extended-theme-name and not -is-provided-theme($extended-theme-name) {
            @error "Invalid extend-theme: \"#{$extended-theme-name}\": only provided themes can be extended, use one of #{meta.inspect(map.keys($-theme-css-files))}";
        }
        @if $extended-theme-name and -is-provided-theme($theme-name) {
            @error "Can't use a provided theme (#{$theme-name}) and also extend a provided theme (#{$extended-theme-name}). If you want to extend #{$extended-theme-name}, provide your own custom theme name.";
        }
        @if $extended-theme-name {
            @include -load-theme-css-file($extended-theme-name);
        }

        $font: map.get($theme-params, "--ag-icon-font-family");
        $font: map.get($-theme-fonts, $theme-name) !default;
        $font: map.get($-theme-fonts, $extended-theme-name) !default;
        @if $font {
            @include -load-css-file("#{$font}Font.css", $ignore-missing: true);
        }
    }

    @each $theme-name, $theme-params in $themes {
        $extended-theme-name: -get-extended-theme-name($theme-params, $default: $global-extended-theme-name);

        @if $extended-theme-name and $extended-theme-name != $theme-name {
            // alias all the styles in the extended theme to the new theme name
            .ag-theme-#{$theme-name} {
                @extend .ag-theme-#{$extended-theme-name};
            }
        }

        // Emit CSS variable declarations for a set of params, converting (foo: bar) to `--ag-foo: bar`
        .ag-theme-#{$theme-name} {
            $color-blend-theme-name: $extended-theme-name;
            $color-blend-theme-name: $theme-name !default;
            $blend-function-name: "-theme-#{$color-blend-theme-name}-color-blends";
            @if meta.function-exists($blend-function-name) {
                $theme-color-blends: meta.get-function($blend-function-name);
                $theme-params: meta.call($theme-color-blends, $theme-params);
            }
            $theme-params: -base-color-blends($theme-params);
        
            @each $name, $value in $theme-params {
                // use meta.inspect to preserve the quote style of strings
                @if map.has-key($-variable-param-types, $name) {
                    #{$name}: #{meta.inspect($value)};
                }
            }
        }
    }
}

//
// PRIVATE IMPLEMENTATION
//

$-loaded-css-files: ();

// Fulfils the same role as meta.load-css, which we can't use due to this issue:
// https://github.com/sass/dart-sass/issues/1627
//
// NOTE the above bug was fixed in Sass 1.52.4 in June 2022, this workaround should
// be kept as long as we want to support earlier Sass versions
@mixin -load-css-file($file, $ignore-missing: false) {
    @if not map.get($-loaded-css-files, $file) {
        $-loaded-css-files: map.set($-loaded-css-files, $file, true);
        @include css-content.output-css-file($file, $ignore-missing: $ignore-missing);
    }
}

@mixin -load-theme-css-file($theme-name, $ignore-missing: false) {
    $css-file: map.get($-theme-css-files, $theme-name);
    @if $css-file {
        @include -load-css-file($css-file);
    }
}

@function -clean-theme-name($name) {
    @if $name == null {
        @return null;
    }
    $name: string.to-lower-case($name);
    $remove-prefix: "ag-theme-";
    @if string.index($name, $remove-prefix) == 1 {
        $name: string.slice($name, string.length($remove-prefix) + 1);
    }
    @return $name;
}

@function -clean-variable-name($name) {
    @if $name == null {
        @return null;
    }
    $name: string.to-lower-case($name);
    
    $proposed-variable-name: "--ag-#{$name}";
    @if map.has-key($-variable-param-types, $proposed-variable-name) {
        $name: $proposed-variable-name;
    }
        
    $remove-prefix: "ag-theme-";
    @if string.index($name, $remove-prefix) == 1 {
        $name: string.slice($name, string.length($remove-prefix) + 1);
    }
    @return $name;
}

@function -get-extended-theme-name($theme-params, $default) {
    $extended-theme-name: -clean-theme-name(map.get($theme-params, "extend-theme"));
    @if $extended-theme-name {
        @return $extended-theme-name;
    }
    @return $default;
}

$-theme-variables: (
    "alpine": (alpine-active-color: "color"),
    "alpine-dark": (alpine-active-color: "color"),
    "balham": (balham-active-color: "color"),
    "balham-dark": (balham-active-color: "color"),
    "material": (material-primary-color: "color", material-accent-color: "color"),
);

$-theme-css-files: (
    "alpine": "ag-theme-alpine-no-font.css",
    "alpine-dark": "ag-theme-alpine-no-font.css",
    "balham": "ag-theme-balham-no-font.css",
    "balham-dark": "ag-theme-balham-no-font.css",
    "material": "ag-theme-material-no-font.css",
);

$-theme-fonts: (
    "alpine": "agGridAlpine",
    "alpine-dark": "agGridAlpine",
    "balham": "agGridBalham",
    "balham-dark": "agGridBalham",
    "material": "agGridMaterial",
);

@function -is-provided-theme($theme-name) {
    @return map.has-key($-theme-variables, $theme-name);
}

@function -get-themes($params) {
    $themes: map.get($params, "themes");
    $themes: () !default;
    
    @if meta.type-of($themes) == string {
        $themes: ($themes,) // comma makes this a single element list
    }
    
    // treat ("alpine", "balham") as (alpine: (), "balham": ())
    @if meta.type-of($themes) == list {
        $themes-list: $themes;
        $themes: ();
        @each $theme in $themes-list {
            $themes: map.set($themes, $theme, ());
        }
    }
    
    @if map.has-key($params, "theme") {
        $theme: map.get($params, "theme");
        @if meta.type-of($theme) == list {
            @error "Expected theme to be a string, got a list (if you intend to use multiple themes, use the plural `themes` parameter)";
        }
        @else if meta.type-of($theme) != string {
            @error "Expected theme to be a string, got #{meta.inspect($theme)})";
        }
        $themes: map.set($themes, $theme, ());
    }
    
    @if list.length($themes) == 0 {
        $themes: (alpine: ());
    }

    $params: map.remove($params, "theme", "themes");

    $cleaned: ();
    @each $name, $extra-params in $themes {
        $theme-params: internal-preprocess-params(map.merge($params, $extra-params));
        $cleaned: map.set($cleaned, -clean-theme-name($name), $theme-params);
    }
    @return $cleaned;
}

// Return a version of $params with colour blending applied
@function -base-color-blends($params) {
    // Simple defaults. We need to define the defaults for any parameters
    // that are used in blending below
    $params: -param-default($params, --ag-foreground-color,            #000);
    $params: -param-default($params, --ag-background-color,            #fff);
    $params: -param-default($params, --ag-range-selection-border-color, --ag-foreground-color);

    // Blended defaults.
    $params: -param-default($params, --ag-disabled-foreground-color,          --ag-foreground-color,                 $alpha: 0.5);
    $params: -param-default($params, --ag-modal-overlay-background-color,     --ag-background-color,                 $alpha: 0.66);
    $params: -param-default($params, --ag-range-selection-background-color,   --ag-range-selection-border-color,     $alpha: 0.2);
    $params: -param-default($params, --ag-range-selection-background-color-2, --ag-range-selection-background-color, $self-overlay: 2);
    $params: -param-default($params, --ag-range-selection-background-color-3, --ag-range-selection-background-color, $self-overlay: 3);
    $params: -param-default($params, --ag-range-selection-background-color-4, --ag-range-selection-background-color, $self-overlay: 4);
    $params: -param-default($params, --ag-border-color,                       --ag-foreground-color,                 $alpha: 0.25);
    $params: -param-default($params, --ag-header-column-separator-color,      --ag-border-color,                     $alpha: 0.5);
    $params: -param-default($params, --ag-header-column-resize-handle-color,  --ag-border-color,                     $alpha: 0.5);
    $params: -param-default($params, --ag-input-disabled-border-color,        --ag-input-border-color,               $alpha: 0.3);
    @return $params;
}

@function -theme-alpine-color-blends($params) {
    // Simple defaults. We need to define the defaults for any parameters
    // that are used in blending either below or in the base color blends
    $params: -param-default($params, --ag-background-color,            #fff);
    $params: -param-default($params, --ag-foreground-color,            #181d1f);
    $params: -param-default($params, --ag-subheader-background-color,  #fff);
    $params: -param-default($params, --ag-alpine-active-color,         #2196f3);
    $params: -param-default($params, --ag-range-selection-border-color, --ag-alpine-active-color);

    // Blended defaults
    $params: -param-default($params, --ag-subheader-toolbar-background-color, --ag-subheader-background-color, $alpha: 0.5);
    $params: -param-default($params, --ag-selected-row-background-color,      --ag-alpine-active-color,        $alpha: 0.1);
    $params: -param-default($params, --ag-row-hover-color,                    --ag-alpine-active-color,        $alpha: 0.1);
    $params: -param-default($params, --ag-column-hover-color,                 --ag-alpine-active-color,        $alpha: 0.1);
    $params: -param-default($params, --ag-chip-background-color,              --ag-foreground-color,           $alpha: 0.07);
    $params: -param-default($params, --ag-input-disabled-background-color,    --ag-border-color,               $alpha: 0.15);
    $params: -param-default($params, --ag-input-disabled-border-color,        --ag-border-color,               $alpha: 0.3);
    $params: -param-default($params, --ag-disabled-foreground-color,          --ag-foreground-color,           $alpha: 0.5);
    $params: -param-default($params, --ag-input-focus-border-color,           --ag-alpine-active-color,        $alpha: 0.4);
    @return $params;
}

@function -theme-alpine-dark-color-blends($params) {
    $params: -param-default($params, --ag-background-color,           #181d1f);
    $params: -param-default($params, --ag-foreground-color,           #fff);
    $params: -param-default($params, --ag-subheader-background-color, #000);

    @return  -theme-alpine-color-blends($params);
}

@function -theme-balham-color-blends($params) {
    // Simple defaults. We need to define the defaults for any parameters
    // that are used in blending either below or in the base color blends
    $params: -param-default($params, --ag-background-color,             #fff);
    $params: -param-default($params, --ag-foreground-color,             #000);
    $params: -param-default($params, --ag-border-color,                 #bdc3c7);
    $params: -param-default($params, --ag-subheader-background-color,   #e2e9eb);
    $params: -param-default($params, --ag-balham-active-color,          #0091ea);
    $params: -param-default($params, --ag-range-selection-border-color, --ag-balham-active-color);

    // Blended defaults
    $params: -param-default($params, --ag-secondary-foreground-color,         --ag-foreground-color,           $alpha: 0.54);
    $params: -param-default($params, --ag-disabled-foreground-color,          --ag-foreground-color,           $alpha: 0.38);
    $params: -param-default($params, --ag-subheader-toolbar-background-color, --ag-subheader-background-color, $alpha: 0.5);
    $params: -param-default($params, --ag-row-border-color,                   --ag-border-color,               $alpha: 0.58);
    $params: -param-default($params, --ag-chip-background-color,              --ag-foreground-color,           $alpha: 0.1);
    $params: -param-default($params, --ag-selected-row-background-color,      --ag-balham-active-color,        $alpha: 0.28);
    $params: -param-default($params, --ag-header-column-separator-color,      --ag-border-color,               $alpha: 0.5);
    @return $params;
}

@function -theme-balham-dark-color-blends($params) {
    $params: -param-default($params, --ag-background-color,           #181d1f);
    $params: -param-default($params, --ag-foreground-color,           #fff);
    $params: -param-default($params, --ag-border-color,               #424242);
    $params: -param-default($params, --ag-subheader-background-color, #000);
    $params: -param-default($params, --ag-balham-active-color,        #00B0FF);

    $params: -param-default($params, --ag-disabled-foreground-color, --ag-foreground-color, $alpha: 0.38);
    $params: -param-default($params, --ag-header-foreground-color,   --ag-foreground-color, $alpha: 0.64);

    @return  -theme-balham-color-blends($params);
}

@function -theme-material-color-blends($params) {
    // Simple defaults. We need to define the defaults for any parameters
    // that are used in blending either below or in the base color blends
    $params: -param-default($params, --ag-background-color,                  #fff);
    $params: -param-default($params, --ag-foreground-color,                  rgba(0, 0, 0, 0.87));
    $params: -param-default($params, --ag-subheader-background-color,        #eee);
    $params: -param-default($params, --ag-material-primary-color,            #3f51b5);
    $params: -param-default($params, --ag-range-selection-border-color,       --ag-material-primary-color);
    $params: -param-default($params, --ag-range-selection-background-color,  rgba(122, 134, 203, 0.1));
    $params: -param-default($params, --ag-border-color,                      #e2e2e2);

    // Blended defaults
    $params: -param-default($params, --ag-secondary-foreground-color,         --ag-foreground-color,           $alpha: 0.54);
    $params: -param-default($params, --ag-disabled-foreground-color,          --ag-foreground-color,           $alpha: 0.38);
    $params: -param-default($params, --ag-subheader-toolbar-background-color, --ag-subheader-background-color, $alpha: 0.5);
    @return $params;
}

// Apply a default value to a parameter
//  $params: -param-default($params, x, #f08) - default x to a specific color value
//  $params: -param-default($params, x, y) - default x to the value of y (if y is set)
//  $params: -param-default($params, x, y, $alpha: 0.5) - default x to the value of y made 50% transparent
@function -param-default($params, $target, $source, $alpha: null, $self-overlay: null) {
    @if string.index($target, "--ag-") != 1 {
        @error "Internal error: $target to -param-default should start --ag-";
    }
    $value: null;
    @if type-of($source) == "color" {
        $value: $source;
    } @else {
        @if string.index($source, "--ag-") != 1 {
            @error "Internal error: $source to -param-default should start --ag-";
        }
        $value: map.get($params, $source);
    }
    @if map.has-key($params, $target) or $value == null {
        @return $params;
    }
    @if $alpha != null {
        $value: color.change($value, $alpha: color.alpha($value) * $alpha);
    }
    @if $self-overlay != null {
        // this formula produces the same opacity value as overlaying the color on top
        // of itself $self-overlay times
        $value: color.change($value, $alpha: 1-(math.pow(1 - color.alpha($value), $self-overlay)));
    }
    @return map.set($params, $target, $value);
}

$-param-type-descriptions: (
    "color": "a CSS color (e.g. `red` or `#fff`)",
    "length": "a CSS length (e.g. `0`, `4px` or `50%`)",
    "border-style": "a CSS border style (e.g. `dotted` or `solid`)",
    "duration": "a number with time duration units (e.g. `3s` or `250ms`)",
    "border-style-and-size": "either `none`, or a CSS border-style and size (e.g. `solid 1px`), or a boolean (true -> `solid 1px` and false -> `none`)",
    "border-style-and-color": "a CSS border-style and color (e.g. `solid red`)",
    "display": "A CSS display value (`block` to show, `none` to hide - `true` and `false` are also accepted)",
    "any": "any value",
);

// params that are copied to --ag-param-name variables
$-variable-param-types: (
    --ag-foreground-color: "color",
    --ag-data-color: "color",
    --ag-secondary-foreground-color: "color",
    --ag-header-foreground-color: "color",
    --ag-disabled-foreground-color: "color",
    --ag-background-color: "color",
    --ag-header-background-color: "color",
    --ag-tooltip-background-color: "color",
    --ag-subheader-background-color: "color",
    --ag-subheader-toolbar-background-color: "color",
    --ag-control-panel-background-color: "color",
    --ag-side-button-selected-background-color: "color",
    --ag-selected-row-background-color: "color",
    --ag-odd-row-background-color: "color",
    --ag-modal-overlay-background-color: "color",
    --ag-row-hover-color: "color",
    --ag-column-hover-color: "color",
    --ag-range-selection-border-color: "color",
    --ag-range-selection-border-style: "border-style",
    --ag-range-selection-background-color: "color",
    --ag-range-selection-background-color-2: "color",
    --ag-range-selection-background-color-3: "color",
    --ag-range-selection-background-color-4: "color",
    --ag-range-selection-highlight-color: "color",
    --ag-selected-tab-underline-color: "color",
    --ag-selected-tab-underline-width: "length",
    --ag-selected-tab-underline-transition-speed: "duration",
    --ag-range-selection-chart-category-background-color: "color",
    --ag-range-selection-chart-background-color: "color",
    --ag-header-cell-hover-background-color: "color",
    --ag-header-cell-moving-background-color: "color",
    --ag-value-change-value-highlight-background-color: "color",
    --ag-value-change-delta-up-color: "color",
    --ag-value-change-delta-down-color: "color",
    --ag-chip-background-color: "color",
    --ag-borders: "border-style-and-size",
    --ag-border-color: "color",
    --ag-borders-critical: "border-style-and-size",
    --ag-borders-secondary: "border-style-and-size",
    --ag-secondary-border-color: "color",

    --ag-row-border-style: "border-style",
    --ag-row-border-width: "length",
    --ag-row-border-color: "color",

    --ag-cell-horizontal-border: "border-style-and-color",
    --ag-borders-input: "border-style-and-size",
    --ag-input-border-color: "color",
    --ag-borders-input-invalid: "border-style-and-size",
    --ag-input-border-color-invalid: "color",
    --ag-borders-side-button: "border-style-and-size",
    --ag-border-radius: "length",
    --ag-header-column-separator-display: "display",
    --ag-header-column-separator-height: "length",
    --ag-header-column-separator-width: "length",
    --ag-header-column-separator-color: "color",
    --ag-header-column-resize-handle-display: "display",
    --ag-header-column-resize-handle-height: "length",
    --ag-header-column-resize-handle-width: "length",
    --ag-header-column-resize-handle-color: "color",
    --ag-invalid-color: "color",
    --ag-input-disabled-border-color: "color",
    --ag-input-disabled-background-color: "color",
    --ag-checkbox-background-color: "color",
    --ag-checkbox-border-radius: "length",
    --ag-checkbox-checked-color: "color",
    --ag-checkbox-unchecked-color: "color",
    --ag-checkbox-indeterminate-color: "color",
    --ag-toggle-button-off-border-color: "color",
    --ag-toggle-button-off-background-color: "color",
    --ag-toggle-button-on-border-color: "color",
    --ag-toggle-button-on-background-color: "color",
    --ag-toggle-button-switch-background-color: "color",
    --ag-toggle-button-switch-border-color: "color",
    --ag-toggle-button-border-width: "length",
    --ag-toggle-button-height: "length",
    --ag-toggle-button-width: "length",
    --ag-input-focus-box-shadow: "any",
    --ag-input-focus-border-color: "color",
    --ag-minichart-selected-chart-color: "color",
    --ag-minichart-selected-page-color: "color",
    --ag-grid-size: "length",
    --ag-icon-size: "length",
    --ag-widget-container-horizontal-padding: "length",
    --ag-widget-container-vertical-padding: "length",
    --ag-widget-horizontal-spacing: "length",
    --ag-widget-vertical-spacing: "length",
    --ag-cell-horizontal-padding: "length",
    --ag-cell-widget-spacing: "length",
    --ag-row-height: "length",
    --ag-header-height: "length",
    --ag-list-item-height: "length",
    --ag-column-select-indent-size: "length",
    --ag-set-filter-indent-size: "length",
    --ag-advanced-filter-builder-indent-size: "length",
    --ag-row-group-indent-size: "length",
    --ag-filter-tool-panel-group-indent: "length",
    --ag-tab-min-width: "length",
    --ag-menu-min-width: "length",
    --ag-side-bar-panel-width: "length",
    --ag-font-family: "any",
    --ag-font-size: "length",
    --ag-card-radius: "length",
    --ag-card-shadow: "any",
    --ag-popup-shadow: "any",
    --ag-alpine-active-color: "color",
    --ag-balham-active-color: "color",
    --ag-material-primary-color: "color",
    --ag-material-accent-color: "color",
    --ag-icon-font-family: "any"
);

@each $icon-name in map.keys($icon-font-codes) {
    $-variable-param-types: map.set($-variable-param-types, "--ag-icon-font-code-#{$icon-name}", "any");
}

// params that are not copied to CSS variables
$-non-variable-param-types: (
    suppress-native-widget-styling: "bool",
    extend-theme: "string"
);

// Apply Sass API sugar over CSS variable API
@function internal-preprocess-params($params) {
    $processed: ();
    @each $name, $value in $params {
        $name: -clean-variable-name($name);
        $type: map.get($-variable-param-types, $name);
        // Allow `null` for colours
        @if $type == "color" and $value == null {
            $value: transparent;
        }
        // Allow booleans for borders params
        @if string.index($name, "borders") and $value == true {
            $value: solid 1px;
        }
        @if string.index($name, "borders") and $value == false {
            $value: none;
        }
        // Allow booleans for display params
        @if $type == "display" and not $value {
            $value: none;
        }
        @if $type == "display" and $value == true {
            $value: block;
        }
        $processed: map.set($processed, $name, $value);
    }
    @return $processed;
}

@mixin -validate-params($params, $theme: null) {
    $error: internal-get-params-error($params, $theme);
    @if $error {
        @error $error;
    }
}

@function internal-get-params-error($params, $theme: null) {
    $theme-variables: ();
    @if $theme and map.has-key($-theme-variables, $theme) {
        $theme-variables: map.get($-theme-variables, $theme);
    }

    $param-types: map.merge($-variable-param-types, $theme-variables);
    $errors: ();
    @each $name, $value in $params {

        @if $name == "suppress-native-widget-styling" {
            $errors: list.append($errors, "suppress-native-widget-styling can not be specified at the theme level, only at the top level");
        }

        @if -is-runtime-expression($value) {
            // You can pass variable expressions as values and we'll just trust
            // that it's an appropriate value because we can't check at compile time
        } @else {
            $expected-type: map.get($param-types, $name);
            @if $expected-type {
                $is-valid: -validate-value($value, $expected-type);

                @if not $is-valid {
                    $expected: map.get($-param-type-descriptions, $expected-type);
                    $expected: $expected-type !default;
                    $errors: list.append($errors, "Invalid parameter `#{$name}: #{meta.inspect($value)}` (expected #{$expected})");
                }
            } @else if not map.has-key($-non-variable-param-types, $name) {
                $errors: list.append($errors, "Unrecognised parameter '#{$name}'");
            }
        }
    }
    @if list.length($errors) > 0 {
        $message: "";
        @each $error in $errors {
            @if $message == "" {
                $message: $error;
            }
            @else {
                $message: "#{$message}; #{$error}";
            }
        }
        @if list.length($errors) > 1 {
            $message: "#{list.length($errors)} errors in #{$theme} parameters: #{$message}";
        }
        @else {
            $message: "Error in #{$theme} parameters: #{$message}";
        }
        @return $message;
    }
    @return null;
}

@function -validate-value($value, $expected-type) {
    $validator-name: "-validate-#{$expected-type}";
    $validator: meta.get-function($validator-name);
    @if not $validator {
        @error "Internal error: no validator function #{$validator-name}";
    }
    @return meta.call($validator, $value);
}

@function -is-runtime-expression($value) {
    $value: string.to-lower-case(meta.inspect($value));
    @return string.index($value, "var(") != null or string.index($value, "calc(") != null;
}

@function -validate-color($value) {
    @return meta.type-of($value) == "color";
}

@function -validate-length($value) {
    @return meta.type-of($value) == "number" and (unit($value) != "" or $value == 0);
}

$border-styles: (none, hidden, dotted, dashed, solid, double, groove, ridge, inset, outset, initial);
@function -validate-border-style($value) {
    $value-preserve-quotes: meta.inspect($value);
    @return list.index($border-styles, $value-preserve-quotes) != null;
}

@function -validate-duration($value) {
    @return meta.type-of($value) == "number" and (unit($value) == "s" or unit($value) == "ms" or $value == 0);
}

@function -validate-display($value) {
    @return $value == block or $value == none;
}

@function -validate-border-style-and-size($value) {
    @return -validate-two-in-any-order($value, "border-style", "length");
}

@function -validate-border-style-and-color($value) {
    @return -validate-two-in-any-order($value, "border-style", "color");
}

@function -validate-bool($value) {
    @return meta.type-of($value) == "bool";
}

@function -validate-string($value) {
    @return meta.type-of($value) == "string";
}

@function -validate-any($value) {
    @return true;
}

@function -validate-two-in-any-order($value, $type-a, $type-b) {
    @if meta.type-of($value) == "string" and $value == "none" {
        @return true;
    }

    @if meta.type-of($value) != "list" or list.length($value) != 2 {
        @return false;
    }

    $value-1: list.nth($value, 1);
    $value-2: list.nth($value, 2);
    @return (
        (-validate-value($value-1, $type-a) and -validate-value($value-2, $type-b))
        or
        (-validate-value($value-2, $type-a) and -validate-value($value-1, $type-b))
    );
}

// check that we defined all the validator functions
@each $variable-name, $type in $-variable-param-types {
    @if not map.has-key($-param-type-descriptions, $type) {
        @error "Internal error: missing type description for #{$type} (used on #{$variable-name})";
    }
    @if not meta.function-exists("-validate-#{$type}") {
        @error "Internal error: missing validator function for #{$type} (used on #{$variable-name})";
    }
}